/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain (a) copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.sentry.ohos.core;

import ohos.aafwk.content.IntentParams;
import ohos.app.Context;
import ohos.event.commonevent.CommonEventData;
import ohos.event.commonevent.CommonEventManager;
import ohos.event.commonevent.CommonEventSubscribeInfo;
import ohos.event.commonevent.CommonEventSubscriber;
import ohos.event.commonevent.MatchingSkills;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.rpc.RemoteException;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;

import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.sentry.Breadcrumb;
import io.sentry.IHub;
import io.sentry.ILogger;
import io.sentry.Integration;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.util.Objects;
import io.sentry.util.StringUtils;

import static ohos.event.commonevent.CommonEventSupport.*;

/**
 * SystemEventsBreadcrumbsIntegration
 *
 * @since 2021-06-01
 */
public final class SystemEventsBreadcrumbsIntegration implements Integration, Closeable {
    static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x01, "sentry.system.event");

    /**
     * subscriber
     */
    @TestOnly
    public SystemCommonEventsSubscriber subscriber;
    private final @NotNull Context context;
    private SentryOhosOptions sentryOhosOptions;
    private final @NotNull List<String> actions;

    /**
     * SystemEventsBreadcrumbsIntegration
     *
     * @param context Context
     */
    public SystemEventsBreadcrumbsIntegration(final @NotNull Context context) {
        this(context, getDefaultActions());
    }

    /**
     * SystemEventsBreadcrumbsIntegration
     *
     * @param context Context
     * @param actions List<String>
     */
    public SystemEventsBreadcrumbsIntegration(
        final @NotNull Context context, final @NotNull List<String> actions) {
        this.context = Objects.requireNonNull(context, "Context is required");
        this.actions = Objects.requireNonNull(actions, "Actions list is required");
    }

    @Override
    public void register(final @NotNull IHub hub, final @NotNull SentryOptions options) {
        Objects.requireNonNull(hub, "Hub is required");
        this.sentryOhosOptions =
            Objects.requireNonNull(
                (options instanceof SentryOhosOptions) ? (SentryOhosOptions) options : null,
                "SentryOhosOptions is required");

        this.sentryOhosOptions
            .getLogger()
            .log(
                SentryLevel.DEBUG,
                "SystemEventsBreadcrumbsIntegration enabled: %s",
                this.sentryOhosOptions.isEnableSystemEventBreadcrumbs());

        if (this.sentryOhosOptions.isEnableSystemEventBreadcrumbs()) {
            MatchingSkills matchingSkills = new MatchingSkills();
            for (String item : actions) {
                matchingSkills.addEvent(item);
            }

            CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
            subscriber = new SystemCommonEventsSubscriber(subscribeInfo, hub, this.sentryOhosOptions.getLogger());
            try {
                CommonEventManager.subscribeCommonEvent(subscriber);
            } catch (RemoteException e) {
                HiLog.error(LABEL, "Exception occurred during subscribeCommonEvent invocation.");
            }

            options.getLogger().log(SentryLevel.DEBUG, "SystemEventsBreadcrumbsIntegration installed.");
        }
    }

    @SuppressWarnings("deprecation")
    private static @NotNull List<String> getDefaultActions() {
        final List<String> actions = new ArrayList<>();
        actions.add(COMMON_EVENT_ABILITY_ADDED);
        actions.add(COMMON_EVENT_ABILITY_REMOVED);
        actions.add(COMMON_EVENT_ABILITY_UPDATED);
        actions.add(COMMON_EVENT_ACCOUNT_DELETED);
        actions.add(COMMON_EVENT_AIRPLANE_MODE_CHANGED);
        actions.add(COMMON_EVENT_BATTERY_CHANGED);
        actions.add(COMMON_EVENT_BATTERY_LOW);
        actions.add(COMMON_EVENT_BATTERY_OKAY);
        actions.add(COMMON_EVENT_BLUETOOTH_A2DPSINK_AUDIO_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_A2DPSINK_CONNECT_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_A2DPSINK_PLAYING_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_A2DPSOURCE_AVRCP_CONNECT_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_A2DPSOURCE_CODEC_VALUE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_A2DPSOURCE_CONNECT_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_A2DPSOURCE_CURRENT_DEVICE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_A2DPSOURCE_PLAYING_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_HANDSFREEUNIT_AG_CALL_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_HANDSFREEUNIT_AG_COMMON_EVENT);
        actions.add(COMMON_EVENT_BLUETOOTH_HANDSFREEUNIT_AUDIO_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_HANDSFREEUNIT_CONNECT_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_HANDSFREE_AG_AUDIO_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_HANDSFREE_AG_CONNECT_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_HANDSFREE_AG_CURRENT_DEVICE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_HOST_DISCOVERY_FINISHED);
        actions.add(COMMON_EVENT_BLUETOOTH_HOST_DISCOVERY_STARTED);
        actions.add(COMMON_EVENT_BLUETOOTH_HOST_NAME_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_HOST_REQ_DISABLE);
        actions.add(COMMON_EVENT_BLUETOOTH_HOST_REQ_DISCOVERABLE);
        actions.add(COMMON_EVENT_BLUETOOTH_HOST_REQ_ENABLE);
        actions.add(COMMON_EVENT_BLUETOOTH_HOST_SCAN_MODE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_HOST_STATE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_ACL_CONNECTED);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_ACL_DISCONNECTED);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_BATTERY_VALUE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_CLASS_VALUE_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_CONNECT_CANCEL);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_CONNECT_REPLY);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_CONNECT_REQ);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_DISCOVERED);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_NAME_UPDATE);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_PAIRING_CANCEL);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_PAIRING_REQ);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_PAIR_STATE);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_SDP_RESULT);
        actions.add(COMMON_EVENT_BLUETOOTH_REMOTEDEVICE_UUID_VALUE);
        actions.add(COMMON_EVENT_BOOT_COMPLETED);
        actions.add(COMMON_EVENT_BUNDLE_REMOVED);
        actions.add(COMMON_EVENT_CHARGING);
        actions.add(COMMON_EVENT_CLOSE_SYSTEM_DIALOGS);
        actions.add(COMMON_EVENT_CONFIGURATION_CHANGED);
        actions.add(COMMON_EVENT_DATE_CHANGED);
        actions.add(COMMON_EVENT_DEVICE_IDLE_MODE_CHANGED);
        actions.add(COMMON_EVENT_DISCHARGING);
        actions.add(COMMON_EVENT_DISK_BAD_REMOVAL);
        actions.add(COMMON_EVENT_DISK_EJECT);
        actions.add(COMMON_EVENT_DISK_MOUNTED);
        actions.add(COMMON_EVENT_DISK_REMOVED);
        actions.add(COMMON_EVENT_DISK_UNMOUNTABLE);
        actions.add(COMMON_EVENT_DISK_UNMOUNTED);
        actions.add(COMMON_EVENT_DRIVE_MODE);
        actions.add(COMMON_EVENT_EXTERNAL_APPLICATIONS_AVAILABLE);
        actions.add(COMMON_EVENT_EXTERNAL_APPLICATIONS_UNAVAILABLE);
        actions.add(COMMON_EVENT_FOUNDATION_READY);
        actions.add(COMMON_EVENT_HOME_MODE);
        actions.add(COMMON_EVENT_HWID_LOGIN);
        actions.add(COMMON_EVENT_HWID_LOGOFF);
        actions.add(COMMON_EVENT_HWID_LOGOUT);
        actions.add(COMMON_EVENT_HWID_TOKEN_INVALID);
        actions.add(COMMON_EVENT_IVI_ACTIVE);
        actions.add(COMMON_EVENT_IVI_LASTMODE_SAVE);
        actions.add(COMMON_EVENT_IVI_SLEEP);
        actions.add(COMMON_EVENT_IVI_STANDBY);
        actions.add(COMMON_EVENT_IVI_TEMPERATURE_ABNORMAL);
        actions.add(COMMON_EVENT_IVI_TEMPERATURE_RECOVERY);
        actions.add(COMMON_EVENT_IVI_VOLTAGE_ABNORMAL);
        actions.add(COMMON_EVENT_IVI_VOLTAGE_RECOVERY);
        actions.add(COMMON_EVENT_LOCALE_CHANGED);
        actions.add(COMMON_EVENT_LOCATION_MODE_STATE_CHANGED);
        actions.add(COMMON_EVENT_LOCKED_BOOT_COMPLETED);
        actions.add(COMMON_EVENT_MANAGE_PACKAGE_STORAGE);
        actions.add(COMMON_EVENT_MY_PACKAGE_REPLACED);
        actions.add(COMMON_EVENT_MY_PACKAGE_SUSPENDED);
        actions.add(COMMON_EVENT_MY_PACKAGE_UNSUSPENDED);
        actions.add(COMMON_EVENT_NFC_ACTION_ADAPTER_STATE_CHANGED);
        actions.add(COMMON_EVENT_NFC_ACTION_RF_FIELD_OFF_DETECTED);
        actions.add(COMMON_EVENT_NFC_ACTION_RF_FIELD_ON_DETECTED);
        actions.add(COMMON_EVENT_OFFICE_MODE);
        actions.add(COMMON_EVENT_PACKAGES_SUSPENDED);
        actions.add(COMMON_EVENT_PACKAGES_UNSUSPENDED);
        actions.add(COMMON_EVENT_PACKAGE_ADDED);
        actions.add(COMMON_EVENT_PACKAGE_CHANGED);
        actions.add(COMMON_EVENT_PACKAGE_DATA_CLEARED);
        actions.add(COMMON_EVENT_PACKAGE_FIRST_LAUNCH);
        actions.add(COMMON_EVENT_PACKAGE_FULLY_REMOVED);
        actions.add(COMMON_EVENT_PACKAGE_NEEDS_VERIFICATION);
        actions.add(COMMON_EVENT_PACKAGE_REMOVED);
        actions.add(COMMON_EVENT_PACKAGE_REPLACED);
        actions.add(COMMON_EVENT_PACKAGE_RESTARTED);
        actions.add(COMMON_EVENT_PACKAGE_VERIFIED);
        actions.add(COMMON_EVENT_POWER_CONNECTED);
        actions.add(COMMON_EVENT_POWER_DISCONNECTED);
        actions.add(COMMON_EVENT_POWER_SAVE_MODE_CHANGED);
        actions.add(COMMON_EVENT_SCREEN_OFF);
        actions.add(COMMON_EVENT_SCREEN_ON);
        actions.add(COMMON_EVENT_SHUTDOWN);
        actions.add(COMMON_EVENT_TIMEZONE_CHANGED);
        actions.add(COMMON_EVENT_TIME_CHANGED);
        actions.add(COMMON_EVENT_TIME_TICK);
        actions.add(COMMON_EVENT_UID_REMOVED);
        actions.add(COMMON_EVENT_USB_ACCESSORY_ATTACHED);
        actions.add(COMMON_EVENT_USB_ACCESSORY_DETACHED);
        actions.add(COMMON_EVENT_USB_DEVICE_ATTACHED);
        actions.add(COMMON_EVENT_USB_DEVICE_DETACHED);
        actions.add(COMMON_EVENT_USER_ADDED);
        actions.add(COMMON_EVENT_USER_BACKGROUND);
        actions.add(COMMON_EVENT_USER_FOREGROUND);
        actions.add(COMMON_EVENT_USER_PRESENT);
        actions.add(COMMON_EVENT_USER_REMOVED);
        actions.add(COMMON_EVENT_USER_STARTED);
        actions.add(COMMON_EVENT_USER_STARTING);
        actions.add(COMMON_EVENT_USER_STOPPED);
        actions.add(COMMON_EVENT_USER_STOPPING);
        actions.add(COMMON_EVENT_USER_SWITCHED);
        actions.add(COMMON_EVENT_USER_UNLOCKED);
        actions.add(COMMON_EVENT_VISIBLE_ACCOUNTS_UPDATED);
        return actions;
    }

    @Override
    public void close() throws IOException {
        if (subscriber != null) {
            try {
                CommonEventManager.unsubscribeCommonEvent(subscriber);
            } catch (RemoteException e) {
                HiLog.error(LABEL, "Exception occurred during unsubscribeCommonEvent invocation.");
            }
            subscriber = null;
            if (sentryOhosOptions != null) {
                sentryOhosOptions.getLogger().log(SentryLevel.DEBUG, "SystemEventsBreadcrumbsIntegration remove.");
            }
        }
    }

    /**
     * SystemCommonEventsSubscriber
     *
     * @since 2021-06-01
     */
    public static final class SystemCommonEventsSubscriber extends CommonEventSubscriber {
        private final @NotNull IHub hub;
        private final @NotNull ILogger logger;

        SystemCommonEventsSubscriber(CommonEventSubscribeInfo info, final @NotNull IHub hub, final @NotNull ILogger logger) {
            super(info);
            this.hub = hub;
            this.logger = logger;
        }

        @Override
        public void onReceiveEvent(CommonEventData commonEventData) {
            final Breadcrumb breadcrumb = new Breadcrumb();
            breadcrumb.setType("system");
            breadcrumb.setCategory("device.event");
            final String action = commonEventData.getIntent().getAction();
            String shortAction = StringUtils.getStringAfterDot(action);
            if (shortAction != null) {
                breadcrumb.setData("action", shortAction);
            }
            final IntentParams intentParams = commonEventData.getIntent().getParams();
            final Map<String, String> newExtras = new HashMap<>();
            if (intentParams != null && !intentParams.isEmpty()) {
                for (String item : intentParams.keySet()) {
                    try {
                        Object value = intentParams.getParam(item);
                        if (value != null) {
                            newExtras.put(item, value.toString());
                        }
                    } catch (Exception exception) {
                        logger.log(
                            SentryLevel.ERROR,
                            exception,
                            "%s key of the %s action threw an error.",
                            item,
                            action);
                    }
                }
                breadcrumb.setData("extras", newExtras);
            }
            breadcrumb.setLevel(SentryLevel.INFO);
            hub.addBreadcrumb(breadcrumb);
        }
    }
}
